/**
 * 
 */
package sim32;

import java.io.File;
import java.io.FileNotFoundException;

/**
 * @author Piotr
 * This is the class representing the pipeline(s). It contains the five pipeline stages,
 * the instruction and data memories, and the hazard controller.
 * The data memory has been moved out of the MEM stage to allow for multiple MEM stages.
 */
public class Pipeline {
	private final int numberOfPipes;
	
	IFStage ifs;
	IDStage ids;
	EXStage[] exs;
	MEMStage[] mems;
	WBStage[] wbs;
	HazardController hzc;
	
	private DataMemory dm;
	
	
	/**
	 * Creates a new Pipeline with the number of parallel pipelines specified. Initializes
	 * the instruction memory with programFile, does not initialize data memory.
	 * @param numberOfPipes The number of parallel pipelines to create.
	 * @param programFile	The program to initialize the instruction memory with.
	 * @throws FileNotFoundException 
	 */
	Pipeline(int numberOfPipes, File programFile) throws FileNotFoundException {
		this.numberOfPipes = numberOfPipes;
		ifs = new IFStage(0, programFile, numberOfPipes);
		ids = new IDStage(numberOfPipes);
		exs = new EXStage[numberOfPipes];
		mems = new MEMStage[numberOfPipes];
		wbs = new WBStage[numberOfPipes];
		for (int ii = 0; ii < numberOfPipes; ii++) {
			exs[ii] = new EXStage(ii);
			mems[ii] = new MEMStage(ii);
			wbs[ii] = new WBStage(ii);
		}
		dm = new DataMemory();
		hzc = new HazardController(numberOfPipes);
	}
	
	/**
	 * Creates a new Pipeline with the number of parallel pipelines specified. Initializes
	 * the instruction memory with programFile, initializes the data memory with dataFile.
	 * @param numberOfPipes The number of parallel pipelines to create.
	 * @param programFile	The program to initialize the instruction memory with.
	 * @param dataFile 		The data file to initialize the data memory with.
	 * @throws FileNotFoundException 
	 */
	Pipeline(int numberOfPipes, File programFile, File dataFile) throws FileNotFoundException {
		this.numberOfPipes = numberOfPipes;
		ifs = new IFStage(0, programFile, numberOfPipes);
		ids = new IDStage(dataFile, numberOfPipes);
		exs = new EXStage[numberOfPipes];
		mems = new MEMStage[numberOfPipes];
		wbs = new WBStage[numberOfPipes];
		for (int ii = 0; ii < numberOfPipes; ii++) {
			exs[ii] = new EXStage(ii);
			mems[ii] = new MEMStage(ii);
			wbs[ii] = new WBStage(ii);
		}
		try {
			dm = new DataMemory(dataFile);
		} catch (FileNotFoundException e) {
			System.out.println("Error reading " + dataFile 
					+ ". Did not initialize memory.");
			dm = new DataMemory();
		}
		hzc = new HazardController(numberOfPipes);
	}
	
	
	/**
	 * The pipeline's cycle methods. Performs the cycle on all of the elements of the
	 * pipeline(s) in the correct order (end to beginning).
	 * @return
	 */
	int cycle() throws InvalidInstructionException {
		int instrsInPipe = 0;
		for (int ii = 0; ii < numberOfPipes; ii++) {
			instrsInPipe += wbs[ii].cycle(mems[ii]);
		}
		for (int ii = 0; ii < numberOfPipes; ii++) {
			instrsInPipe += mems[ii].cycle(exs[ii]);
		}
		for (int ii = 0; ii < numberOfPipes; ii++) {
			instrsInPipe += exs[ii].cycle(ids);
		}
		updateEXStalls();
		instrsInPipe += ids.cycle(ifs);
		instrsInPipe += ifs.cycle(null);
		return instrsInPipe;
	}
	
	/**
	 * Method to update the stalled status of the pipeline following a stall caused
	 * by an EXStage. This had to be moved out of the HazardController so that
	 * all EX stages could cycle() before the stalled setting was set, keeping them
	 * synchronized.
	 */
	void updateEXStalls() {
		boolean EXStalls = false;
		for (int ii = 0; ii < numberOfPipes; ii++) {
			if (exs[ii].causedStall == true) {
				EXStalls = true;
			}
		}
		if (EXStalls) {
			for (int ii = 0; ii < numberOfPipes; ii++) {
				Sim.pipe.exs[ii].stalled = true;
			}
			Sim.pipe.ids.stalled = true;
			Sim.pipe.ifs.stalled = true;
		}

	}

	
	/**
	 * Method to read from the data memory.
	 * @param address
	 * @return
	 */
	int readDM(int address) {
		return dm.read(address);
	}
	
	/**
	 * Method to write to the data memory.
	 * @param address
	 * @param data
	 */
	void writeDM(int address, int data) {
		dm.write(address, data);
	}
	
	/**
	 * Returns a memory dump as a string.
	 * @return
	 */
	String dumpMemory() {
		return dm.toString();
	}
	
	/**
	 * Returns the number of non-nop/null instructions in the WB stages.
	 */
	public int numWBInstrs() {
		int total = 0;
		for (int ii = 0; ii < numberOfPipes; ii++) {
			if (wbs[ii].getInstruction() != null && !wbs[ii].getInstruction().isNop()) {
				total++;
			}
		}
		return total;
	}
}
